package org.mytools.dao;

import java.io.File;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.velocity.VelocityContext;
import org.mytools.dao.gener.DomainWrapper;
import org.mytools.dao.gener.MapperWrapper;
import org.mytools.dao.gener.meta.ColumnData;
import org.mytools.dao.gener.meta.MetaData;
import org.mytools.util.FileUtils;
import org.mytools.util.StringUtils;

public class Organizer {

	private static final String VM_PATH = "org/mytools/dao/vm/";

	private DaoGenerator generator;
	private VelocityContext context;
	private Parameter parameter;
	private Connection connection;
	private MetaData metaData;

	private Organizer(String[] params) {
		this.generator = new DaoGenerator();
		this.context = new VelocityContext();
		this.parameter = new Parameter(params);
		this.wrapperParameters(this.context);
		this.initConnection();
		this.initMetaData();
	}

	static class Parameter {
		static final String MetaClass = "metaClass";
		static final String JDBCUrl = "jdbcUrl";
		static final String JDBCUser = "jdbcUser";
		static final String JDBCPassword = "jdbcPassword";
		static final String JDBCDriver = "jdbcDriver";
		static final String DomainPackage = "domainPackage";
		static final String DomainPath = "domainPath";
		static final String MapperPath = "mapperPath";
		static final String TablePrefix = "tablePrefix";
		static final String TableList = "tableList";
		static final String HasMultiDataSource = "hasMultiDataSource";
		static final String SqlSessionTemplateName = "sqlSessionTemplateName";
		static final String EnabledForeginKeysJoinSelect = "enabledForeginKeysJoinSelect";

		private static final String ParameterSeparator = "=";
		private Map<String, String> parameters;

		Parameter(String[] params) {
			this.setParameters(params);
			this.validParameters();
		}

		Parameter() {
		}

		void setParameters(String[] params) {
			Map<String, String> parameters = new HashMap<String, String>();
			for (String param : params) {
				parameters.putAll(StringUtils.splitToMap(param,
						ParameterSeparator));
			}
			this.parameters = parameters;
		}

		Map<String, String> getParameters() {
			return this.parameters;
		}

		String getParameter(String key) {
			return this.parameters.get(key);
		}

		boolean isContains(String key) {
			return this.getParameters().containsKey(key);
		}

		void validParameters() {
			if (this.isContains(Parameter.TableList)
					&& this.isContains(Parameter.TablePrefix)) {
				throw new RuntimeException(
						"Can't contains 'tableList' and 'tablePrefix' both.");
			}
		}
	}

	private void initMetaData() {
		try {
			String metaClass = this.parameter.getParameter(Parameter.MetaClass);
			Class<?> clazz = Class.forName(metaClass);
			Class<?>[] parameterTypes = { Connection.class };
			Constructor<?> constructor = clazz.getConstructor(parameterTypes);
			Object[] parameters = { this.connection };
			this.metaData = (MetaData) constructor.newInstance(parameters);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void wrapperParameters(VelocityContext context) {
		Set<String> keys = this.parameter.getParameters().keySet();
		for (String key : keys) {
			if (key.equals(Parameter.HasMultiDataSource)
					|| key.equals(Parameter.EnabledForeginKeysJoinSelect)) {
				context.put(key, Boolean.valueOf(this.parameter.getParameters()
						.get(key)));
			} else {
				context.put(key, this.parameter.getParameters().get(key));
			}
		}
	}

	private void initConnection() {
		try {
			String url = this.parameter.getParameter(Parameter.JDBCUrl);
			String username = this.parameter.getParameter(Parameter.JDBCUser);
			String password = this.parameter
					.getParameter(Parameter.JDBCPassword);
			String driver = this.parameter.getParameter(Parameter.JDBCDriver);

			Class.forName(driver).newInstance();
			this.connection = DriverManager.getConnection(url, username,
					password);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void releaseConnection() {
		try {
			if (this.connection != null)
				this.connection.close();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	private void generateDomain(String tableName) {
		String domainPath = this.parameter.getParameter(Parameter.DomainPath);
		String domainName = DomainWrapper.getDomainName(tableName);
		File domain = FileUtils.createFile(domainPath + File.separator
				+ domainName + DomainWrapper.getDomainSuffix());

		this.context.put("DomainWrapper", DomainWrapper.class);
		this.context.put("domainName", domainName);
		this.generator.generate(
				this.context,
				DomainWrapper.getDomainTemplate(VM_PATH
						+ this.metaData.getDBName()), domain);
	}

	private void generateMapper(String tableName) {
		String mapperPath = this.parameter.getParameter(Parameter.MapperPath);
		String mapperName = MapperWrapper.getMapperName(DomainWrapper
				.getDomainName(tableName));
		File mapper = FileUtils.createFile(mapperPath + File.separator
				+ mapperName + MapperWrapper.getMapperSuffix());

		this.context.put("MapperWrapper", MapperWrapper.class);
		this.context.put("mapperName", mapperName);
		this.context.put("tableName", tableName);
		this.context.put("tableDomainName",
				DomainWrapper.getDomainPropertyName(tableName));
		this.generator.generate(
				this.context,
				MapperWrapper.getMapperTemplate(VM_PATH
						+ this.metaData.getDBName()), mapper);
	}

	private List<String> filterTableNames() throws SQLException {
		List<String> tableNames = null;

		if (this.parameter.isContains(Parameter.TableList)) {
			tableNames = StringUtils.splitToList(
					this.parameter.getParameter(Parameter.TableList), ",");
		} else if (this.parameter.isContains(Parameter.TablePrefix)) {
			tableNames = this.metaData.getTableNames(this.parameter
					.getParameter(Parameter.TablePrefix));
		} else {
			tableNames = this.metaData.getTableNames();
		}

		return tableNames;
	}

	private void perform() {
		try {
			List<String> tableNames = filterTableNames();
			for (String table : tableNames) {
				List<ColumnData> columnList = this.metaData.getColumns(table);
				this.context.put("columnList", columnList);

				generateDomain(table);
				generateMapper(table);
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			releaseConnection();
		}
	}

	public static void main(String[] args) {
		Organizer organizer = new Organizer(args);
		organizer.perform();
	}

}
